package com.study.chunguard.common.utils.datatools;

import com.study.chunguard.common.model.Result;
import com.study.chunguard.common.utils.datatools.model.ExcelImportResult;
import com.study.chunguard.common.validation.ValidateUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 导入 excel 数据工具类，用于处理导入数据的校验和批量保存，提供了最佳实践的模版方法。
 * <p>
 * 用法示例：
 *
 * <blockquote>
 * <pre>{@code
 * @RestController
 * @RequestMapping("/whitelistVehicles")
 * public class WhitelistVehicleController {
 *
 *     @Resource
 *     private WhitelistVehicleService whitelistVehicleService;
 *
 *     @Operation(summary = "白名单车辆导入")
 *     @PostMapping("/import")
 *     public Result<ExcelImportResult> importWhitelist(@RequestParam("file") MultipartFile file) {
 *         // 采集导入结果信息
 *         ExcelImportResult importResult = ExcelImportResult.createInstance();
 *
 *         EasyExcelHelper.consumeExcelRows(file, WhitelistVehicleImportVo.class,
 *                 (rows, startRowIndex, endRowIndex) ->
 *                         whitelistVehicleService.importWhitelist(rows, startRowIndex, endRowIndex, importResult),
 *                 1);
 *
 *         return Result.ok(importResult);
 *     }
 * }
 *
 *  * 导入白名单车辆
 *  *
 *  * @param importRows    导入的数据
 *  * @param startRowIndex 开始行索引
 *  * @param endRowIndex   结束行索引
 *  * @param errorMessages 错误消息列表
 *  @Override
 *  public void importWhitelist(List<WhitelistVehicleImportVo> importRows, int startRowIndex, int endRowIndex, ExcelImportResult importResult) {
 *      ImportUtils.importData(importRows, startRowIndex, endRowIndex, importResult,
 *              WhitelistVehicleImportVo::getVehPlate,
 *              keys -> ivWhitelistVehicleApi.listByVehiclePlates(keys).getCheckedData().stream().map(IvWhitelistVehicleRespDto::getVehPlate).collect(Collectors.toSet()),
 *              WhitelistVehicleConverter.INSTANCE::fromImportVoToReqDto,
 *              validSaveDataList -> ivWhitelistVehicleApi.batchCreate(validSaveDataList)
 *      );
 *  }
 * }</pre></blockquote>
 *
 * @author zhaowenhao
 * @since 2024-07-22
 */
public class ImportUtils {

    private ImportUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 导入 Excel 数据的模版方法，用于处理导入数据的校验和批量保存。
     *
     * @param <I>                 导入数据类型
     * @param <S>                 保存数据类型
     * @param importRows          导入的 excel 数据行
     * @param startRowIndex       开始 excel 行号
     * @param endRowIndex         结束 excel 行号
     * @param importResult        导入结果对象
     * @param getImportRowKeyFunc 导入数据获取唯一索引的函数
     * @param collectExistingKeys 通过导入数据的唯一索引列表查询数据库已存在的索引 Set 的函数，输入参数为导入数据的唯一索引列表，输出为数据库中已存在的唯一索引集合
     * @param toSaveDataFunc      将导入数据转换为保存数据的函数
     * @param batchSaveFunc       批量保存数据的函数
     */
    @SuppressWarnings("java:S107")
    public static <I, S> void importData(List<I> importRows, int startRowIndex, int endRowIndex, ExcelImportResult importResult,
                                         Function<I, String> getImportRowKeyFunc,
                                         Function<List<String>, Set<String>> collectExistingKeys,
                                         Function<I, S> toSaveDataFunc,
                                         Function<List<S>, Result<Boolean>> batchSaveFunc) {
        List<S> validSaveDataList = new ArrayList<>();
        List<String> validErrorMessages = new ArrayList<>();
        AtomicInteger rowIndex = new AtomicInteger(startRowIndex);
        Set<String> seenKeys = new HashSet<>();

        // 收集所有唯一索引用于数据库对比
        List<String> keys = importRows.stream()
                .map(getImportRowKeyFunc)
                .collect(Collectors.toList());

        // 使用 IN 查询从数据库中检查这些唯一索引是否已存在
        Set<String> existingKeys = collectExistingKeys.apply(keys);

        importRows.forEach(importRow -> {
            int currentIndex = rowIndex.getAndIncrement();
            String uniqueKey = getImportRowKeyFunc.apply(importRow);

            // 字段校验
            List<String> errors = ValidateUtils.getErrors(importRow);
            if (!errors.isEmpty()) {
                validErrorMessages.add("第 " + currentIndex + " 行导入失败，原因: " + String.join(", ", errors));
                return;
            }

            // 列表内自检
            if (!seenKeys.add(uniqueKey)) {
                validErrorMessages.add("第 " + currentIndex + " 行导入失败，原因: [" + uniqueKey + "] 在文件中重复");
                return;
            }

            // 数据库对比
            if (existingKeys != null && existingKeys.contains(uniqueKey)) {
                validErrorMessages.add("第 " + currentIndex + " 行导入失败，原因: [" + uniqueKey + "] 已存在");
                return;
            }

            // 构建创建数据
            S saveData = toSaveDataFunc.apply(importRow);
            validSaveDataList.add(saveData);
        });

        // 批量保存
        if (!validSaveDataList.isEmpty()) {
            Result<Boolean> result = batchSaveFunc.apply(validSaveDataList);
            // 如果批量保存事务失败，所有记录都未保存，直接返回，不需要添加检验产生的错误信息
            if (!Boolean.TRUE.equals(result.getData())) {
                importResult.incrementFailNum(importRows.size());
                importResult.addErrorMessage("从第 " + startRowIndex + " 行到第 " + endRowIndex + " 行导入失败，原因: " + result.getMsg());
                return;
            }
        }

        // 添加成功行数
        importResult.incrementSuccessNum((validSaveDataList.size()));
        // 添加失败行数
        importResult.incrementFailNum(importRows.size() - validSaveDataList.size());
        // 添加检验产生的错误信息
        importResult.addErrorMessages(validErrorMessages);
    }
}
